#include "bitarray.h"

ostream& operator<< (ostream& os , BitArray& a)
{
	uint64 tmp = a.getSize();
	for(uint64  i = 0;i<tmp; i++)
	{
		if(a[tmp-1-i])
			os <<"1";
		else
			os <<"0";
	}
	return os;
}


BitArray::BitArray()
{
	copy = true;
	byteLen = 1;
	trueSize = (byteLen+7) & -8;
	buff = new byte [trueSize];
	for(uint32 i = 0; i < byteLen; i++)
		buff[i] = 0;
	length = ((uint64)byteLen)<<3;
	clearPadding();
	generateExtraInfo();
}

BitArray::BitArray(char* filename)
{
	copy = true;
	ifstream is;
	is.open (filename, ios::binary );
	is.seekg (0, ios::end);
	byteLen = is.tellg();
	is.seekg (0, ios::beg);
	trueSize = (byteLen+7) & -8;
	buff = new byte [trueSize];
	is.read (buff,byteLen);
	is.close();
	length = ((uint64)byteLen)<<3;
	clearPadding();
	generateExtraInfo();
		
}

void BitArray::clear()
{
	for(uint64 i = 0;i < trueSize; i++)
		buff[i] = 0;
			
}

bool BitArray::isClear()
{
	for (uint32 i  = 0; i < byteLen; i++)
		if(buff[i])
	  		return false;
	return true;
}

BitArray::BitArray(byte* b,uint32 len,bool cpy)
{
	copy = cpy;
	byteLen = len;
	trueSize = (byteLen+7) & -8;
	
	if(copy){
		buff = new byte [trueSize];
		for(uint32 i = 0; i < byteLen; i++)
		buff[i] = b[i];
	}
	else
		buff = b;
	length = ((uint64)byteLen)<<3;
	clearPadding();
	generateExtraInfo();
}

BitArray::BitArray(uint32 len,bool val)
{
	copy = true;
	byteLen = len;
	trueSize = (byteLen+7) & -8;
	buff = new byte [trueSize];
	for(uint32 i = 0; i < byteLen; i++)
		buff[i] = val?0xFF:0;
	length = ((uint64)byteLen)<<3;
	clearPadding();
	generateExtraInfo();
}


BitArray::BitArray(const BitArray &b) 
{
	if(this != &b)
	{
		copy = b.copy;
		delete [] buff;
		length = b.length;
		byteLen = b.byteLen;
		trueSize = b.trueSize;
		buff = new byte [trueSize];
		for(uint32 i = 0; i < trueSize; i++)
			buff[i] = b.buff[i];
		generateExtraInfo();
	}
}

BitArray& BitArray::operator=(const BitArray &b)
{
	if(this != &b)
	{
		copy = b.copy;
		delete [] buff;
		length = b.length;
		byteLen = b.byteLen;
		trueSize = b.trueSize;
		buff = new byte [trueSize];
		for(uint32 i = 0; i < trueSize; i++)
			buff[i] = b.buff[i];
		generateExtraInfo();
	}
	return *this;
}

BitArray::~BitArray()
{
	if(copy)
		delete [] buff;
}

void BitArray::clearPadding()
{
	for(uint32 i = byteLen; i < trueSize;i++)
		buff[i] = 0;
}

uint32 BitArray::getTrueSize()
{
	return trueSize;
}

uint64 BitArray::countBits(bool type)
{
  uint64 counter = 0;
  for (uint32 i  = 0; i < byteLen; i++)
	for (uint32 j = 0; j < 8; j++)
		if ((buff[i] >> j) & 1)
			counter++;
  return type?counter:length-counter;
}

void BitArray::generateExtraInfo()
{
	extraInfo.lowerSize64 = (byteLen & -8)>>3;
	extraInfo.firstOverIndex8 = extraInfo.lowerSize64 << 3;
	extraInfo.buff64 = (uint64*) buff;
	extraInfo.lastElem64 = extraInfo.buff64 + (((trueSize>>3)==extraInfo.lowerSize64)?extraInfo.lowerSize64-1:extraInfo.lowerSize64);
}

bool BitArray::operator==(BitArray &b)
{ 
	if(byteLen != b.getByteSize())
		return false;
	uint32 i;
	for(i = 0; i < extraInfo.lowerSize64; i++)
		if(extraInfo.buff64[i] != b.getUint64(i))
			return false;
	for(i=extraInfo.firstOverIndex8; i < byteLen; i++)
		if(buff[i] !=  b.getByte(i))
			return false;
	return true;
}

bool BitArray::operator<(BitArray &b)
{ 
	if(getMinByteSize() != b.getMinByteSize())
		return (getMinByteSize() < b.getMinByteSize());
	
	int64 i;
	for(i =byteLen; i >= extraInfo.firstOverIndex8; i--)
		if(buff[i] !=  b.getByte(i))
			return (buff[i] <  b.getByte(i));	

	for(i = extraInfo.lowerSize64; i >= 0; i--)
		if(extraInfo.buff64[i] != b.getUint64(i))
			return (extraInfo.buff64[i] < b.getUint64(i));
	
	return false;
}

bool BitArray::arithmIsEqual(BitArray &b)
{
	BitArray* large = this;
	BitArray* small = &b;
	if(small->getByteSize() > large->getByteSize())
	{
		small = this;
		large = &b;
	}
	uint32 smallSize = small->getByteSize();
	uint32 largeSize = large->getByteSize();
	for(uint32 i = smallSize; i < largeSize; i++)
		if(large->getByte(i))
			return false;	
	for(uint32 i = 0; i < smallSize; i++)
		if(large->getByte(i) != small->getByte(i))
			return false;
	return true;

}

uint32 BitArray::getByteSize()
{
	return byteLen;
}

bool BitArray::operator!=(BitArray &b)
{
	return !((*this)== b);
}

byte BitArray::getByte(uint32 index)
{
	return buff[index];
}

uint64 BitArray::getUint64(uint32 index)
{
	return extraInfo.buff64[index];
}

void BitArray::operator++()
{
	bignum_inc(extraInfo.buff64,extraInfo.lastElem64);
}

void BitArray::operator++(int32)
{
	++(*this);
}

void BitArray::operator+=(uint64 index)
{
	for(uint64 i = 0; i < index;i++)
		++(*this);
}

bool BitArray::operator[](uint64 index)
{
	return (((buff[index>>3])>>(index&7))&1);
}

void BitArray::setBit(bool b,uint64 idx)
{

	if(idx<length)
	{	
		ubyte i = 1;
		if(b)
			(buff[idx>>3]) |= (i << (idx&7));
		else
			(buff[idx>>3]) &= ~(i << (idx&7));
	}
}

uint64 BitArray::getSize()
{
	return length;
}

uint32 BitArray::getMinByteSize()
{
	uint32 i ;
	for(i = byteLen-1; i > 0; i--)
		if(buff[i])
			break;
	return i+1;
}

uint64 BitArray::getMinBitSize()
{
	uint32 i ;
	
	for(i = byteLen-1; i > 0; i--)
		if(buff[i])
			break;
	
	uint32 j = 0;
	uint32 l = (ubyte) buff[i];
	while(l)
	{
		l >>= 1;
		j++;
	}
	if(!j)
		j++;
	return  ((uint64)j)+((((uint64)i)*8));
}


void BitArray::bignum_inc(uint64 *beg,uint64* end)
{
	if (beg != (end+1))
	{
		(*beg)++;
		if (!(*beg))
			bignum_inc(beg+1,end);
	}
}

void BitArray::fill(uint64 ln,bool val)
{
	uint32 bytes = (ln>>3); 
	uint32 bits0  = ln&7; 
	uint32 bits64 = (bytes & -8)>>3; 
	uint64 top64=0;
	top64--;
	for(uint32 i=0; i < bits64;i++)
		extraInfo.buff64[i] = val?top64:0;
	for(uint32 i=(bits64<<3);i < bytes;i++)
		buff[i] = val?0xFF:0;
	if(val)
		for(int32 i=0;i<bits0;i++)
			buff[bytes] |= (1 << i);
	else
		for(int32 i=0;i<bits0;i++)
			buff[bytes] &= ~(1 << i);
}

void BitArray::crapHash512(ubyte* sum)
{
	uint32 i,j,k;
	for(i=0,k=0; i < extraInfo.lowerSize64; i+=8,k++)
	{
		sum[k] = extraInfo.buff64[0];
		for(j= 1; j < 8; j++)
			sum[k]  ^= *(extraInfo.buff64+i);
	}
}

void BitArray::write(byte* filename)
{
	ofstream os;
	os.open (filename, ios::binary /*| ios::app*/);
	os.write(buff,getMinByteSize());
	os.close();
}

void BitArray::write(byte* filename,uint32 len)
{
	ofstream os;
	os.open (filename, ios::binary /*| ios::app*/);
	os.write(buff,len);
	os.close();
}





uint32 BitArray::getApproxCompressedSize()
{	
	
	
	int level = Z_BEST_COMPRESSION;

    
    z_stream strm;
    unsigned char out[CHUNK];
	

    /* allocate deflate state */
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    deflateInit(&strm, level);

    /* compress until end of file */

    strm.avail_in = this->getByteSize();
	strm.next_in = (unsigned char* ) buff;

	/* run deflate() on input until output buffer not full, finish
	   compression if all of source has been read in */
	do {
		//unsigned have;
		strm.avail_out = CHUNK;
		strm.next_out = out;
		deflate(&strm, Z_FINISH);
		//have = CHUNK - strm.avail_out;
		//fwrite (out, sizeof (char), have, stdout);
	} while (strm.avail_out == 0);
	
	uint32 size = strm.total_out;
    (void)deflateEnd(&strm);
	
  
	return size;
}
